//
// Copyright (c) 2017-2022 CNRS INRIA
//

#ifndef __pinocchio_macros_hpp__
#define __pinocchio_macros_hpp__

#include <sstream>

// On Windows, __cplusplus is not necessarily set to the C++ version being used.
// See https://docs.microsoft.com/fr-fr/cpp/build/reference/zc-cplusplus?view=vs-2019 for further
// information.

#if (__cplusplus >= 202002L)
  #define PINOCCHIO_WITH_CXX20_SUPPORT
#endif

#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703))
  #define PINOCCHIO_WITH_CXX17_SUPPORT
#endif

#if (__cplusplus >= 201402L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201403))
  #define PINOCCHIO_WITH_CXX14_SUPPORT
#endif

#if (__cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1600))
  #define PINOCCHIO_WITH_CXX11_SUPPORT
#endif

#define PINOCCHIO_STRING_LITERAL(string) #string

// For more details, visit
// https://stackoverflow.com/questions/171435/portability-of-warning-preprocessor-directive
#if defined(__GNUC__) || defined(__clang__)
  #define PINOCCHIO_PRAGMA(x) _Pragma(#x)
  #define PINOCCHIO_PRAGMA_MESSAGE(the_message) PINOCCHIO_PRAGMA(GCC message #the_message)
  #define PINOCCHIO_PRAGMA_WARNING(the_message) PINOCCHIO_PRAGMA(GCC warning #the_message)
  #define PINOCCHIO_PRAGMA_DEPRECATED(the_message)                                                 \
    PINOCCHIO_PRAGMA_WARNING(Deprecated : #the_message)
  #define PINOCCHIO_PRAGMA_DEPRECATED_HEADER(old_header, new_header)                               \
    PINOCCHIO_PRAGMA_WARNING(Deprecated header file : #old_header has been replaced                \
                               by #new_header.\n Please use #new_header instead of #old_header.)
#else
  #define PINOCCHIO_PRAGMA(x)
  #define PINOCCHIO_PRAGMA_MESSAGE(the_message)
  #define PINOCCHIO_PRAGMA_WARNING(the_message)
  #define PINOCCHIO_PRAGMA_DEPRECATED(the_message)
  #define PINOCCHIO_PRAGMA_DEPRECATED_HEADER(old_header, new_header)
#endif

// This macro can be used to prevent from macro expansion, similarly to EIGEN_NOT_A_MACRO
#define PINOCCHIO_NOT_A_MACRO

namespace pinocchio
{
  namespace helper
  {
    template<typename T>
    struct argument_type;
    template<typename T, typename U>
    struct argument_type<T(U)>
    {
      typedef U type;
    };
  } // namespace helper
} // namespace pinocchio

/// \brief Empty macro argument
#define PINOCCHIO_MACRO_EMPTY_ARG

/// \brief Helper to declare that a parameter is unused
#define PINOCCHIO_UNUSED_VARIABLE(var) (void)(var)

/// Ensure that a matrix (or vector) is of correct size (compile-time and run-time assertion)
#define PINOCCHIO_ASSERT_MATRIX_SPECIFIC_SIZE(type, M, nrows, ncols)                               \
  EIGEN_STATIC_ASSERT(                                                                             \
    (type::RowsAtCompileTime == Eigen::Dynamic || type::RowsAtCompileTime == nrows)                \
      && (type::ColsAtCompileTime == Eigen::Dynamic || type::ColsAtCompileTime == ncols),          \
    THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE);                                          \
  assert(M.rows() == nrows && M.cols() == ncols);

/// Static assertion.
/// \param condition a boolean convertible expression
/// \param msg a valid C++ variable name.
#define PINOCCHIO_STATIC_ASSERT(condition, msg)                                                    \
  {                                                                                                \
    int msg[(condition) ? 1 : -1]; /*avoid unused-variable warning*/                               \
    (void)msg;                                                                                     \
  }

namespace pinocchio
{
  namespace helper
  {
    template<typename D, template<typename> class TypeAccess>
    struct handle_return_type_without_typename
    {
      typedef typename TypeAccess<typename argument_type<void(D)>::type>::type type;
    };
  } // namespace helper
} // namespace pinocchio

/// \brief macros for pragma push/pop/ignore deprecated warnings
#if defined(__GNUC__) || defined(__clang__)
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push")
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop")
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_DEPRECECATED_DECLARATIONS                          \
    _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_VARIADIC_MACROS                                    \
    _Pragma("GCC diagnostic ignored \"-Wvariadic-macros\"")
  #if defined(__clang__)
    #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_MAYBE_UNINITIALIZED
    #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_SELF_ASSIGN_OVERLOADED                           \
      _Pragma("GCC diagnostic ignored \"-Wself-assign-overloaded\"")
  #else
    #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_MAYBE_UNINITIALIZED                              \
      _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
    #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_SELF_ASSIGN_OVERLOADED
  #endif
#elif defined(WIN32)
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_PUSH _Pragma("warning(push)")
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_POP _Pragma("warning(pop)")
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_DEPRECECATED_DECLARATIONS                          \
    _Pragma("warning(disable : 4996)")
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_VARIADIC_MACROS
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_SELF_ASSIGN_OVERLOADED
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_MAYBE_UNINITIALIZED
#else
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_PUSH
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_POP
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_DEPRECECATED_DECLARATIONS
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_VARIADIC_MACROS
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_SELF_ASSIGN_OVERLOADED
  #define PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_MAYBE_UNINITIALIZED
#endif // __GNUC__ || __clang__

// Handle explicitely the GCC boring warning: 'anonymous variadic macros were introduced in C++11'
#include <exception>
#include <stdexcept>
#include <sstream>

#if defined(__GNUC__)
  #pragma GCC system_header
#endif

PINOCCHIO_COMPILER_DIAGNOSTIC_PUSH
PINOCCHIO_COMPILER_DIAGNOSTIC_IGNORED_VARIADIC_MACROS

#if WIN32
  #define PINOCCHIO_PRETTY_FUNCTION __FUNCSIG__
#else
  #define PINOCCHIO_PRETTY_FUNCTION __PRETTY_FUNCTION__
#endif

/// \brief Generic macro to throw an exception in Pinocchio if the condition is not met with a given
/// input message.
#if !defined(PINOCCHIO_NO_THROW)
  #define PINOCCHIO_THROW(condition, exception_type, message)                                      \
    if (!(condition))                                                                              \
    {                                                                                              \
      throw exception_type(message);                                                               \
    }

  #define PINOCCHIO_THROW_PRETTY(exception, message)                                               \
    {                                                                                              \
      std::stringstream ss;                                                                        \
      ss << "From file: " << __FILE__ << "\n";                                                     \
      ss << "in function: " << PINOCCHIO_PRETTY_FUNCTION << "\n";                                  \
      ss << "at line: " << __LINE__ << "\n";                                                       \
      ss << "message: " << message << "\n";                                                        \
      throw exception(ss.str());                                                                   \
    }
#else
  #define PINOCCHIO_THROW(condition, exception_type, message)
  #define PINOCCHIO_THROW_PRETTY(exception, message)
#endif

#define _PINOCCHIO_EXPAND(x) x
#define _PINOCCHIO_GET_OVERRIDE_FOR_CHECK_INPUT_ARGUMENT(_1, _2, MACRO_NAME, ...) MACRO_NAME

#define _PINOCCHIO_CHECK_INPUT_ARGUMENT_2(condition, message)                                      \
  PINOCCHIO_THROW(condition, std::invalid_argument, message)

#define _PINOCCHIO_CHECK_INPUT_ARGUMENT_1(condition)                                               \
  _PINOCCHIO_CHECK_INPUT_ARGUMENT_2(                                                               \
    condition, "The following check on the input argument has failed: " #condition)

#define _PINOCCHIO_CHECK_INPUT_ARGUMENT_0

/// \brief Macro to check an assert-like condition and throw a std::invalid_argument exception (with
/// a message) if violated.
#define PINOCCHIO_CHECK_INPUT_ARGUMENT(...)                                                        \
  _PINOCCHIO_EXPAND(_PINOCCHIO_EXPAND(_PINOCCHIO_GET_OVERRIDE_FOR_CHECK_INPUT_ARGUMENT(            \
    __VA_ARGS__, _PINOCCHIO_CHECK_INPUT_ARGUMENT_2, _PINOCCHIO_CHECK_INPUT_ARGUMENT_1,             \
    _PINOCCHIO_CHECK_INPUT_ARGUMENT_0))(__VA_ARGS__))

#define _PINOCCHIO_GET_OVERRIDE_FOR_CHECK_ARGUMENT_SIZE(_1, _2, _3, MACRO_NAME, ...) MACRO_NAME

#define _PINOCCHIO_CHECK_ARGUMENT_SIZE_3(size, expected_size, message)                             \
  if (size != expected_size)                                                                       \
  {                                                                                                \
    std::ostringstream oss;                                                                        \
    oss << "wrong argument size: expected " << expected_size << ", got " << size << std::endl;     \
    oss << "hint: " << message << std::endl;                                                       \
    PINOCCHIO_THROW(false, std::invalid_argument, oss.str());                                      \
  }

#define _PINOCCHIO_CHECK_ARGUMENT_SIZE_2(size, expected_size)                                      \
  _PINOCCHIO_CHECK_ARGUMENT_SIZE_3(                                                                \
    size, expected_size,                                                                           \
    PINOCCHIO_STRING_LITERAL(size) " is different from " PINOCCHIO_STRING_LITERAL(expected_size))

#define _PINOCCHIO_CHECK_ARGUMENT_SIZE_1

/// \brief Macro to check if the size of an element is equal to the expected size.
#define PINOCCHIO_CHECK_ARGUMENT_SIZE(...)                                                         \
  _PINOCCHIO_EXPAND(_PINOCCHIO_EXPAND(_PINOCCHIO_GET_OVERRIDE_FOR_CHECK_ARGUMENT_SIZE(             \
    __VA_ARGS__, _PINOCCHIO_CHECK_ARGUMENT_SIZE_3, _PINOCCHIO_CHECK_ARGUMENT_SIZE_2,               \
    _PINOCCHIO_CHECK_ARGUMENT_SIZE_1))(__VA_ARGS__))

PINOCCHIO_COMPILER_DIAGNOSTIC_POP

#endif // ifndef __pinocchio_macros_hpp__
